package cheme.ProcessEditor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.RootEditPart;

/**
 * Originally implemented in 
 * edu.wsu.cheme.ProcessEditor#LeafTraversingGraphicalKeyHandler and derived from 
 * code in org.eclipse.gef.ui.parts#GraphicalViewerKeyHandler, this 'navigator'
 * takes as input for its primal process a current edt part to be navigated from,
 * and a direction to be navigated in.
 * 
 * A list of leaf nodes in the view graph will be generated anda  next edit part 
 * selected from those.
 * 
 * This class compares the origin figure's 'direction-most point' (for isntance
 * NORTH-most if the up key was pressed, EAST-most if right) in absolute coordiantes 
 * against all other leave's oppposite direction-most points to choose the closest 
 * figure in a given direction.
 */

public class LeafFigureNavigator
{
	public static void navigateTo(GraphicalEditPart part)
	{
		/*
		 * Make sure all the editpart bits and peices are up to date;
		 */
//		getViewer(part).flush();
		
		internalNavigateTo(part);
	}
	
	/**
	 * Traverses to the closest EditPart in the given list that is also in the given direction.
	 * 
	 * @param	event		the KeyEvent for the keys that were pressed to trigger this traversal
	 * @param	direction	PositionConstants.* indicating the direction in which to traverse
	 */
	public static boolean navigateNearestSibling(GraphicalEditPart epStart, int direction)
	{
		/*
		 * Make sure all the editpart bits and peices are up to date;
		 */
//		getViewer(epStart).flush();
		
		GraphicalEditPart next = findNearestSibling(epStart, direction);
		
		if (next == null)
		{
			return false;
		}
		
		internalNavigateTo(next);
		return true;
	}
	
	/**
	 * Traverses to the closest EditPart in the given list that is also in the given direction.
	 * 
	 * @param	event		the KeyEvent for the keys that were pressed to trigger this traversal
	 * @param	direction	PositionConstants.* indicating the direction in which to traverse
	 */
	public static boolean navigateFarthestSibling(GraphicalEditPart epStart, int direction)
	{
		/*
		 * Make sure all the editpart bits and peices are up to date;
		 */
//		getViewer(epStart).flush();
		
		GraphicalEditPart next = findFarthestSibling(epStart, direction);
		
		if (next == null)
		{
			return false;
		}
		
		internalNavigateTo(next);
		return true;
	}
	
	/**
	 * Returns the list of editparts which are conceptually at the same level of navigation as
	 * the currently focused editpart.  By default, these are the siblings of the focused 
	 * part.
	 * <p>
	 * This implementation returns a list that contains the EditPart that has focus.
	 * </p>
	 * @return a list of navigation editparts
	 */
	protected static List getNavigationSiblings(EditPart focusPart)
	{
		EditPart root = getRoot(focusPart);

		return getLeaves(root);
	}
	
	protected static EditPart getRoot(EditPart part)
	{
		EditPart root = part;
		while(null != root.getParent())
		{
			root = root.getParent();
		}
		
		return root;
	}
	
	private static List getLeaves(EditPart root)
	{
		List leaves = new ArrayList();
		
		if(0 == root.getChildren().size())
		{
			leaves = new ArrayList();
			leaves.add(root);
			
			if(0 != ((GraphicalEditPart)root).getSourceConnections().size())
			{
				for(Object o : ((GraphicalEditPart)root).getSourceConnections())
				{
					leaves.addAll(getLeaves((EditPart)o));
				}
			}
		}
		else
		{
			for(Object o : root.getChildren())
			{
				leaves.addAll(getLeaves((EditPart)o));
			}
		}
		
		return leaves;
	}
	
	/**
	 * Figures' navigation points are used to determine their direction compared to one 
	 * another, and the distance between them.
	 *  
	 * @return	the direction-most point on the figure
	 */
	private static Point getNavigationPoint(IFigure figure, int direction)
	{
		Rectangle bounds = figure.getBounds();
		Point naviPoint = null;
		
		switch(direction)
		{
			case PositionConstants.NORTH :
			{
				naviPoint = new Point(bounds.getCenter().x, bounds.y);
				break;
			}
			case PositionConstants.EAST :
			{
				naviPoint = new Point(bounds.right(), bounds.getCenter().y);
				break;
			}
			case PositionConstants.SOUTH :
			{
				naviPoint = new Point(bounds.getCenter().x, bounds.bottom());
				break;
			}
			case PositionConstants.WEST :
			{
				naviPoint = new Point(bounds.x, bounds.getCenter().y);
				break;
			}
			default :
			{
				naviPoint = bounds.getCenter();
			}
		}
		
		return naviPoint;
	}
	
	/**
	 * Given an absolute point (pStart) and a list of EditParts, this method finds the closest
	 * EditPart (except for the one to be excluded) in the given direction.
	 * 
	 * @param	siblings	List of sibling EditParts
	 * @param	pStart		The starting point (must be in absolute coordinates) from which
	 * 						the next sibling is to be found.
	 * @param	direction	PositionConstants
	 * @param	exclude		The EditPart to be excluded from the search
	 * 
	 */
	protected static GraphicalEditPart findNearestSibling(GraphicalEditPart epStart, int direction)
	{
		IFigure figure = epStart.getFigure();
		List siblings = getNavigationSiblings(epStart);
		
		Point pStart = getNavigationPoint(figure, direction);
		figure.translateToAbsolute(pStart);
		
		GraphicalEditPart epCurrent;
		GraphicalEditPart epFinal = null;

		Point pCurrent;

		int distance = Integer.MAX_VALUE;

		Iterator iter = siblings.iterator();
		while (iter.hasNext())
		{
			epCurrent = (GraphicalEditPart)iter.next();
			if (epCurrent == epStart || !epCurrent.isSelectable())
			{
				continue;
			}
			
			figure = epCurrent.getFigure();
			pCurrent = getNavigationPoint(figure, opposite(direction));
			figure.translateToAbsolute(pCurrent);
			if (!isInRightDirection(pStart, pCurrent, direction))
			{
				continue;
			}

			int d = pCurrent.getDistanceOrthogonal(pStart);
			
			if(d <= 1) //break out if we are that close.
			{
				epFinal = epCurrent;
				break;
			}
			else if (d < distance) //else mark the closest distance and continue
			{
				distance = d;
				epFinal = epCurrent;
			}
		}
		return epFinal;
	}
	
	/**
	 * Given an absolute point (pStart) and a list of EditParts, this method finds the closest
	 * EditPart (except for the one to be excluded) in the given direction.
	 * 
	 * @param	siblings	List of sibling EditParts
	 * @param	pStart		The starting point (must be in absolute coordinates) from which
	 * 						the next sibling is to be found.
	 * @param	direction	PositionConstants
	 * @param	exclude		The EditPart to be excluded from the search
	 * 
	 */
	protected static GraphicalEditPart findFarthestSibling(GraphicalEditPart epStart, int direction)
	{
		IFigure figure = epStart.getFigure();
		List siblings = getNavigationSiblings(epStart);
		
		Point pStart = getNavigationPoint(figure, direction);
		figure.translateToAbsolute(pStart);
		
		GraphicalEditPart epCurrent;
		GraphicalEditPart epFinal = null;

		Point pCurrent;
		
		int distance = 0;

		Iterator iter = siblings.iterator();
		while (iter.hasNext())
		{
			epCurrent = (GraphicalEditPart)iter.next();
			if (epCurrent == epStart || !epCurrent.isSelectable())
			{
				continue;
			}
			
			figure = epCurrent.getFigure();
			pCurrent = getNavigationPoint(figure, opposite(direction));
			figure.translateToAbsolute(pCurrent);
			if (!isInRightDirection(pStart, pCurrent, direction))
			{
				continue;
			}

			int d = getDirectionalDistance(pCurrent, pStart, direction);
			
			if (d > distance) //mark the farthest distance and continue
			{
				distance = d;
				epFinal = epCurrent;
			}
		}
		return epFinal;
	}
	
	protected static int getDirectionalDistance(Point lhs, Point rhs, int direction)
	{
		switch(direction)
		{
			case PositionConstants.NORTH : return getVerticalDistance(lhs, rhs);
			case PositionConstants.SOUTH : return getVerticalDistance(lhs, rhs);
			case PositionConstants.EAST : return getHorizonatalDistance(lhs, rhs);
			case PositionConstants.WEST : return getHorizonatalDistance(lhs, rhs);
			default : throw new Error();
		}
	}
	
	protected static int getVerticalDistance(Point lhs, Point rhs)
	{
		return Math.abs(lhs.y - rhs.y);
	}
	
	protected static int getHorizonatalDistance(Point lhs, Point rhs)
	{
		return Math.abs(lhs.x - rhs.x);
	}
	
	private static int opposite(int direction)
	{
		switch(direction)
		{
			case PositionConstants.NORTH : return PositionConstants.SOUTH;
			case PositionConstants.EAST : return PositionConstants.WEST;
			case PositionConstants.SOUTH : return PositionConstants.NORTH;
			case PositionConstants.WEST : return PositionConstants.EAST;
			default : return PositionConstants.CENTER;
		}
	}
	
	private static boolean isInRightDirection(Point pStart, Point pCurrent, int direction)
	{
		boolean result = false;
		
		switch(direction)
		{
			case PositionConstants.NORTH :
			{
				if(pCurrent.y <= pStart.y)
				{
					result = true;
				}
				break;
			}
			case PositionConstants.EAST :
			{
				if(pCurrent.x >= pStart.x)
				{
					result = true;
				}
				break;
			}
			case PositionConstants.SOUTH :
			{
					if(pCurrent.y >= pStart.y)
				{
					result = true;
				}
				break;
			}
			case PositionConstants.WEST :
			{
					if(pCurrent.x <= pStart.x)
				{
					result = true;
				}
				break;
			}
		}
		
		return result;
	}
	
	/**
	 * Navigates to the given EditPart
	 * 
	 * @param	part	the EditPart to navigate to
	 * @param	event	the KeyEvent that triggered this traversal
	 */
	protected static void internalNavigateTo(GraphicalEditPart part)
	{
		if (part == null)
		{
			return;
		}
		
//		if(null != event && (event.stateMask & SWT.SHIFT) != 0)
//		{
//			getViewer().appendSelection(part);
//			getViewer().setFocus(part);
//		}
//		else if(null != event && (event.stateMask & SWT.CONTROL) != 0)
//		{
//			getViewer().setFocus(part);
//		}
//		else
//		{
//			getViewer(part).select(part);
//		}
		
		try
		{
			GraphicalViewer viewer = getViewer(part); 
			viewer.select(part);
			viewer.reveal(part);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public static void deselect(GraphicalEditPart part)
	{
		GraphicalViewer viewer = getViewer(part); 
		viewer.deselect(part);
	}
	
	public static void deselectAll(GraphicalEditPart part)
	{
		GraphicalViewer viewer = getViewer(part); 
		viewer.deselectAll();
	}
	
	protected static GraphicalViewer getViewer(GraphicalEditPart part)
	{
		return (GraphicalViewer)getRoot(part).getViewer();
	}
	
	public static Viewport getViewport(GraphicalEditPart part)
	{
		EditPart root = getRoot(part);
		
		if(root instanceof RootEditPart)
		{
			return (Viewport)((GraphicalEditPart)root).getFigure();
		}
		
		return null;
	}
}
